
package at.fhj.itm09.snake.views;

import java.util.ArrayList;
import java.util.Random;

import android.content.Context;
import android.content.res.Resources;
import android.util.AttributeSet;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.TextView;
import at.fhj.itm09.snake.R;
import at.fhj.itm09.snake.logic.Coordinate;
import at.fhj.itm09.snake.logic.RefreshHandler;


/**
 * @author Susanne Brandstätter - Susanne.Brandstaetter.ITM09@fh-joanneum.at
 * @author Katharina Biela - Katharina.Biela.ITM09@fh-joanneum.at
 *
 * This view is called by the play activity.
 * It contains all logic to play the game
 */
public class SnakeView extends TileView {
	
	private Bundle state;

	private int snakeHeadColor;
	
    private static final String TAG = "SnakeView";
    private int mode = READY;
    
    /**
     * Some variables to save the current mode of the game.
     * PAUSE: the game is in pause
     * READY: ready to run
     * RUNNING: it already runs
     * LOST: you have already lost the game
     */
    public static final int PAUSE = 0;
    public static final int READY = 1;
    public static final int RUNNING = 2;
    public static final int LOSE = 3;

    /**
     * Some variables to save the current direction of the snake
     */
    private int direction = NORTH;
    private int nextDirection = NORTH;
    private static final int NORTH = 1;
    private static final int SOUTH = 2;
    private static final int EAST = 3;
    private static final int WEST = 4;

    /**
     * Labels for the drawables that will be loaded into the TileView class
     */
    private static final int RED = 1;
    private static final int YELLOW = 2;
    private static final int GREEN = 3;
    private static final int WALL = 4;

    /**
     * is used to save the number of apples which are already eaten. 
     */
    private long score = 0;
    
    /**
     * number of milliseconds between the snake movements. This will decrease
     * by eating apples
     */
    private long moveDelay = 600;
    
    /**
     * text that is shown sometimes
     */
    private TextView statusText;
    
    /**
     * snakeTrail: 	a list of Coordinates that contains the coordinates 
     * 				where the snake is
     * appleList: the secret location of the juicy apples the snake craves.
     */
    private ArrayList<Coordinate> snakeTrail = new ArrayList<Coordinate>();
    private ArrayList<Coordinate> appleList = new ArrayList<Coordinate>();

    /**
     * To place the apples in an random way
     */
    private static final Random RNG = new Random();

    /**
     * instance of a new refresh handler
     */
    private RefreshHandler redrawHandler = new RefreshHandler(this);

    /**
     * Constructs a SnakeView based on inflation from XML
     * 
     * @param context
     * @param attrs
     */
    public SnakeView(Context context, AttributeSet attrs) {
        super(context, attrs);
        		
        initSnakeView();
   }

    public SnakeView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        initSnakeView();
    }

    private void initSnakeView() {
    			
        setFocusable(true);

        Resources r = this.getContext().getResources();
        super.resetTiles(5);
        
        //Loads all drawables
        loadTile(RED, r.getDrawable(R.drawable.red));
        loadTile(YELLOW, r.getDrawable(R.drawable.yellow));
        loadTile(GREEN, r.getDrawable(R.drawable.green));
        loadTile(WALL, r.getDrawable(R.drawable.blue));

    }


    private void initNewGame() {
        snakeTrail.clear();
        appleList.clear();

        //For beginning we create a snake with 6 drawables, 
        //which turns SOUTH at the first time
        snakeTrail.add(new Coordinate(7, 7));
        snakeTrail.add(new Coordinate(6, 7));
        snakeTrail.add(new Coordinate(5, 7));
        snakeTrail.add(new Coordinate(4, 7));
        snakeTrail.add(new Coordinate(3, 7));
        snakeTrail.add(new Coordinate(2, 7));
        nextDirection = SOUTH;

        // We start with two apples
        addRandomApple();
        addRandomApple();

        moveDelay = 600;
        score = 0;
    }

    

    /**
     * Sets the headcolor for the snake which were chosen in the menu
     * @param headColor
     */
    public void setHeadColorForSnake(int headColor){
    	this.snakeHeadColor = headColor;
    }
    

    /**
     * If we want to save the state we have to convert the coordinates into int.
     */
    private ArrayList<Integer> saveCoordinatesToInt(ArrayList<Coordinate> coordinates){
    	ArrayList<Integer> intList = new ArrayList<Integer>();
    	
    	for(Coordinate coordinate : coordinates){
    		intList.add(coordinate.x);
    		intList.add(coordinate.y);
    	}
    	
    	return intList;
    }
    
    /**
     * Saves the game state so that the user does not lose anything
     * if the game process is killed while we are in the 
     * background.
     * 
     * @return a Bundle with this view's state
     */
    public Bundle saveState() {
    	state = new Bundle();
    	
        state.putIntegerArrayList("appleList", saveCoordinatesToInt(appleList));
        state.putInt("direction", Integer.valueOf(direction));
        state.putInt("nextDirection", Integer.valueOf(nextDirection));
        state.putLong("moveDelay", Long.valueOf(moveDelay));
        state.putLong("score", Long.valueOf(score));
        state.putIntegerArrayList("snakeTrail", saveCoordinatesToInt(snakeTrail));
        
        return state;
    }
    
    public Bundle getState(){
    	return this.state;
    }


    /**
     * To restore the game state we have to convert the integer arraylist into 
     * a arraylist of coordinates again. 
     */
    private ArrayList<Coordinate> saveIntToCoordinates(ArrayList<Integer> integers){
    	ArrayList<Coordinate> coordinateList = new ArrayList<Coordinate>();
    	int size = integers.size();
    
    	for(int i = 0; i<size; i += 2){
    		Coordinate c = new Coordinate(integers.get(i), integers.get(i+1));
    		coordinateList.add(c);
    	}
    	
    	return coordinateList;
    }
    
    /**
     * Restore game state if our process is being relaunched
     * 
     * @param state a Bundle containing the game state
     */
    public void restoreState(Bundle state) {
        setMode(PAUSE);

        appleList = saveIntToCoordinates(state.getIntegerArrayList("appleList"));
        direction = state.getInt("direction");
        nextDirection = state.getInt("nextDirection");
        moveDelay = state.getLong("moveDelay");
        score = state.getLong("score");
        snakeTrail = saveIntToCoordinates(state.getIntegerArrayList("snakeTrail"));
    }
    
    
    /**
     * Handels the key events in the game. This method is also called if
     * a button during the game is pressed. 
     * It is based on the DPAD and changes the direction of the snake. It also
     * ignores events, if the snake is turning back to the direction it comes from
     * (e.g. if the snake is moving right, the left event will be ignored).
     */
    
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent msg) {

    	/*
    	 * First we have to check the up event, because our game will
    	 * start and continue if you press up. 
    	 * So in this statement we have to check, if the user want to (re)start
    	 * a game or if the game is already running and he only wants that
    	 * the snake moves up. 
    	 */
        if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
        	
        	/*
        	 * At the beginning or at the end of the game, a new one will 
        	 * be started. 
        	 */
            if (mode == READY | mode == LOSE) {
                initNewGame();
                setMode(RUNNING);
                return (true);
            }

            /*
             * If the game is paused, the snake should continue where it has stopped. 
             */
            if (mode == PAUSE) {
                setMode(RUNNING);
                return (true);
            }

            /*
             * If the game is running, then move the snake south, if the user presses
             * up and the current direction is not north;
             */
            if (direction != SOUTH) {
                nextDirection = NORTH;
            }
            
            return (true);
        }

        /*
         * Move the snake south, except the current direction is north.
         */
        if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
            if (direction != NORTH) {
                nextDirection = SOUTH;
            }
            return (true);
        }

        /*
         * Move the snake west, except the current direction is east.
         */
        if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
            if (direction != EAST) {
                nextDirection = WEST;
            }
            return (true);
        }

        /*
         * Move the snake east, except the current direction is west.
         */
        if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
            if (direction != WEST) {
                nextDirection = EAST;
            }
            return (true);
        }
        
        return super.onKeyDown(keyCode, msg);
    }

    /**
     * Sets the TextView, which shows information to the user
     * 
     * @param newView
     */
    public void setTextView(TextView newView) {
        statusText = newView;
    }

    
    /**
     * A method to control the current mode (RUNNING, PAUSED, ...) of the game. 
     * It also sets the visibility of the textview. 
     * 
     * @param newMode
     */
    public void setMode(int newMode) {
    	
        int oldMode = this.mode;
        this.mode = newMode;

        //Let's (re)start the game
        if (newMode == RUNNING & oldMode != RUNNING) {
            statusText.setVisibility(View.INVISIBLE);
            update();
            return;
        }

        Resources res = getContext().getResources();
        String text = "";
        if (newMode == PAUSE) {
            text = res.getText(R.string.mode_pause).toString();
        }
        if (newMode == READY) {
            text = res.getText(R.string.mode_ready).toString();
        }
        if (newMode == LOSE) {
            text = res.getString(R.string.mode_lose_prefix) + 
                  res.getString(R.string.mode_lose_suffix);
        }

        statusText.setText(text);
        statusText.setVisibility(View.VISIBLE);
        
    }

    /**
     * A method to set a apple to a random place in the game. 
     * It also controls if the apple is under the snake.
     */
    private void addRandomApple() {
        Coordinate newCoordinate = null;
        boolean found = false;
        
        //While we haven't found a good place for an apple
        while (found == false) {
        	
            // Choose a new location for the apple
            int newX = 1 + RNG.nextInt(xTileCountInScreen - 2);
            int newY = 1 + RNG.nextInt(yTileCountInScreen - 2);
            newCoordinate = new Coordinate(newX, newY);

            // Check if the apple is under the snake
            // if it is under the snake set collision to true
            boolean collision = false;
            int snakelength = snakeTrail.size();
            
            for (int i = 0; i < snakelength; i++) {
                if (snakeTrail.get(i).equals(newCoordinate)) {
                    collision = true;
                }
            }

            // if collision is false here, we have found a good place for the apple.
            // if it is true, then we have to go through the while loop again.
            found = !collision;
        }
        if (newCoordinate == null) {
            Log.e(TAG, "Somehow ended up with a null newCoordinate!");
        }
                
        appleList.add(newCoordinate);
    }

    /**
     * This method handels all updates for the game. 
     * It removes all old tiles, it updates the walls, it updates the placement
     * for the apples, updates the location of the snake and sets the move delay
     * for the RefreshHandler.
     */
    public void update() {
        if (mode == RUNNING) {

            clearTiles();
            updateWalls();
            updateSnake();
            updateApples();

            // Sets the time how long the redraw handler should wait
            // until he draws the snake again
            redrawHandler.sleep(this.moveDelay);
        }
    }

    /**
     * Draws some walls.
     */
    private void updateWalls() {
        for (int x = 0; x < xTileCountInScreen; x++) {
        	//Draws the top wall
            setTile(WALL, x, 0);
            //Draws the bottom wall
            setTile(WALL, x, yTileCountInScreen - 1);
        }
        for (int y = 1; y < yTileCountInScreen - 1; y++) {
        	//Draws the left wall
            setTile(WALL, 0, y);
            //Draws the right wall
            setTile(WALL, xTileCountInScreen - 1, y);
        }
        
    }

    /**
     * Draws some apples.
     */
    private void updateApples() {
        for (Coordinate c : this.appleList) {
            setTile(GREEN, c.x, c.y);
        }
    }

    
    /**
     * This method handels the snake. First of all it figures out which way the snake is
     * going, to can see if it runs into an apple, the wall or itself. If there is no 
     * collision, the snake gets a new part in the front and the last part of the snake
     * get removed, except it eats an apple. This makes it possible to simulate the motion
     * of the snake. 
     */
    private void updateSnake() {
        boolean growSnake = false;

        // grow the snake by the head
        Coordinate head = snakeTrail.get(0);
        Coordinate newHead = new Coordinate(1, 1);

        direction = nextDirection;

        if(direction == EAST){
            newHead = new Coordinate(head.x + 1, head.y);      	
        } else if(direction == WEST){
            newHead = new Coordinate(head.x - 1, head.y);       	
        } else if(direction == NORTH){
            newHead = new Coordinate(head.x, head.y - 1);      	
        } else if(direction == SOUTH){
            newHead = new Coordinate(head.x, head.y + 1);     	
        }
        
        

        // Collision detection
        // Checks if the snake is moved into the wall
        if ((newHead.x < 1) || (newHead.y < 1) || (newHead.x > xTileCountInScreen - 2)
                || (newHead.y > yTileCountInScreen - 2)) {
            setMode(LOSE);
            return;

        }

        // Checks if the snake has a collision with its body
        int snakelength = snakeTrail.size();
        
        for (int i = 0; i < snakelength; i++) {
            Coordinate c = snakeTrail.get(i);
            if (c == newHead) {
                setMode(LOSE);
                return;
            }
        }

        // Looks for apples
        int applecount = appleList.size();
        
        for (int i = 0; i < applecount; i++) {
            Coordinate c = appleList.get(i);
            
            if (c.equals(newHead)) {
                appleList.remove(c);
                addRandomApple();

                score += 1;
                moveDelay *= 0.9;

                growSnake = true;
            }
        }

        
        /* 
         * Everytime this method is called, should be a new part of the tail be added.
         * This makes it possible that the snake grows.
         */ 
        snakeTrail.add(0, newHead);
        
        /*
         * We also have to remove the last part of the tail. This has the effect, that
         * the snake is moving forward. But we don't have to do this, if we want that 
         * the snake grows
         */
        if (growSnake == false) {
            snakeTrail.remove(snakeTrail.size() - 1);
        }

        
        //sets the drawables to the right coordinates
        int i = 0;
        for (Coordinate c : snakeTrail) {
            if (i == 0) {
                setTile(snakeHeadColor, c.x, c.y);
            } else {
                setTile(RED, c.x, c.y);
            }
            i++;
        }
    }
}